package nachos.vm;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;
import nachos.vm.*;
import java.util.*;
/**
 * A <tt>UserProcess</tt> that supports demand-paging.
 */
public class VMProcess extends UserProcess {
    /**
     * Allocate a new process.
     */
    public VMProcess() {
	super();
//	lockTlb = new Lock();
    }

    /**
     * Save the state of this process in preparation for a context switch.
     * Called by <tt>UThread.saveState()</tt>.
     */
    public void saveState() {
	super.saveState();
	/*jill add*/
	syncPages();
	invalidateTlb();
	/*jill end*/
    }

    /**
     * Restore the state of this process after a context switch. Called by
     * <tt>UThread.restoreState()</tt>.
     */
    public void restoreState() {
//	super.restoreState();
    }

    /**
     * Initializes page tables for this process so that the executable can be
     * demand-paged.
     *
     * @return	<tt>true</tt> if successful.
     */
    /*jill add*/
    protected boolean loadSections() {
    	pageTable = new TranslationEntry[numPages];
    	additionPageTable = new AdditionInfoTE[numPages];
    	int CoffPages = 0;
    	for (int s=0; s<coff.getNumSections(); s++) {
    	    CoffSection section = coff.getSection(s);
    		CoffPages += section.getLength();
    	}
    	for (int i = 0;i<numPages;i++)
    	{
    		pageTable[i] = new TranslationEntry(i,-1,
    				 false,false,false,false);
    		additionPageTable[i] = new AdditionInfoTE();
       		if (i<CoffPages)
       		{
       			additionPageTable[i].type = cofffile;
       		}
       		else if(i<CoffPages+stackPages)
    		{
    			additionPageTable[i].type = stack;
    		}
    		else
    		{
    			additionPageTable[i].type = argument;
    		}    		
    	}
    	return true;
    }
    /*jill end*/
    /**
     * Release any resources allocated by <tt>loadSections()</tt>.
     */
    protected void unloadSections() {
	super.unloadSections();
/*jill*/	invalidateTlb();
    Lock lock  = VMKernel.lock;
    lock.acquire();
    for (int i=0;i<numPages;i++)
    {
    	if (additionPageTable[i].swapPage!=-1)
    		VMKernel.freeSwapPages.add(additionPageTable[i].swapPage);
    }
    lock.release();
    }    

    /**
     * Handle a user exception. Called by
     * <tt>UserKernel.exceptionHandler()</tt>. The
     * <i>cause</i> argument identifies which exception occurred; see the
     * <tt>Processor.exceptionZZZ</tt> constants.
     *
     * @param	cause	the user exception that occurred.
     */
    public void handleException(int cause) {
	Processor processor = Machine.processor();

	switch (cause) {
	/*jill add*/
	case Processor.exceptionTLBMiss:
		int vaddr = processor.readRegister(Processor.regBadVAddr);
		Lock lock = UserKernel.lock;
		lock.acquire();
		handleTlbMiss(vaddr);
		lock.release();
		break;
	/*jill end*/
	default:
	    super.handleException(cause);
	    break;
	}
    }
    /*jill add*/
    private boolean evictPage(int vpn)
    {
    	boolean dirty = pageTable[vpn].dirty;
    	int offset = pageTable[vpn].ppn * pageSize;
    	int status = 0;
    	if (dirty)
    	{
//    		VMKernel.semaphoreSwapFile.P();
    		List<Integer> freeSwapPages = VMKernel.freeSwapPages;
    		OpenFile swapFile = VMKernel.swapFile;
    		byte[] memory = Machine.processor().getMemory();
    		int swapPages = swapFile.length()/pageSize;
    		if(freeSwapPages.isEmpty())
    		{
    			for (int i=0;i<Machine.processor().getNumPhysPages();i++)
    			{
    				freeSwapPages.add(swapPages+i);
    			}
    		}
    		int slot = freeSwapPages.remove(0);
		    status = VMKernel.swapFile.write(slot*pageSize, memory,offset,pageSize);
		    if (status==-1)
		    {
		    	Lock lock = VMKernel.lock;
		    	lock.release();
			    this.hasError = true;
			    handleExit(errorCode);
		    	return false;
		    }
//		    VMKernel.semaphoreSwapFile.V();
		    additionPageTable[vpn].swapPage = slot;
		    
    	}
    	pageTable[vpn].valid = false;
    	pageTable[vpn].used = false;
    	pageTable[vpn].dirty = false;
    	Processor processor = Machine.processor();
    	TranslationEntry entry;
    	int tlbSize = processor.getTLBSize();
//    	lockTlb.acquire();
    	for(int i = 0;i<tlbSize;i++)
    	{
    	   	entry = processor.readTLBEntry(i);
    	   	if (entry.valid && (entry.vpn == vpn)
    	   		&&(entry.ppn == pageTable[vpn].ppn))
    	   	{
    	   		entry.valid = false;
    	   		processor.writeTLBEntry(i, entry);
    	   		break;
    	   	}
    	}
    	return true;
    }
    private void syncPages()
    {
		int tlbSize = Machine.processor().getTLBSize();
		TranslationEntry entry;
		for(int i = 0;i < tlbSize;i++)
		{
//			lockTlb.acquire();
			entry = Machine.processor().readTLBEntry(i);
//			lockTlb.release();
			if (entry.valid)
			{
				VMProcess process = VMKernel.invertPageTable[entry.ppn].process;
				process.pageTable[entry.vpn].dirty = entry.dirty;
				process.pageTable[entry.vpn].used  = entry.used;
			//	VMKernel.invertPageTable[entry.ppn].used = entry.used;
			  /*pageTable[entry.vpn].dirty = entry.dirty;
				pageTable[entry.vpn].used = entry.used;*/
		    	/*
		    	Lock lockIpt = VMKernel.lockIpt;
		    	lockIpt.acquire();
		    	boolean intStatus = Machine.interrupt().disable();
		    	VMKernel.invertPageTable[entry.ppn].used = entry.used;
		    	//lockIpt.release();
		    	Machine.interrupt().restore(intStatus);
		    	//pageTable[entry.vpn].used = entry.used;*/
			}
		}

    }
    private void invalidateTlb()
    {
    	Processor processor = Machine.processor();
    	TranslationEntry entry;
    	int tlbSize = processor.getTLBSize();
//    	lockTlb.acquire();
    	for(int i = 0;i<tlbSize;i++)
    	{
    	   	entry = processor.readTLBEntry(i);
        	entry.valid = false;
        	processor.writeTLBEntry(i, entry);    		
    	}
//    	lockTlb.release();
    }
    private int handleTlbMiss(int vaddr)
    {
    	Processor processor = Machine.processor();
		Random random = new Random();
		int tlbSize = processor.getTLBSize();
		int tlbReplace = random.nextInt(tlbSize);
		int vpn = vaddr/pageSize;
//        System.out.println(vaddr);
		if (!pageTable[vpn].valid)
		{
			List<Integer> list = UserKernel.freePages;
			int ppn;
			InvertTranslationEntry ite;
//			boolean intStatus = Machine.interrupt().disable();
			if(!list.isEmpty())
			{
				ppn = list.remove(0);
//				sem.V();
			}
			else
			{
//				sem.V();				
				syncPages();
				ite = VMKernel.nextPage();
				ppn = ite.ppn;
				ite.process.evictPage(ite.vpn);
			}
			loadPage(vpn,ppn);
//			Machine.interrupt().restore(intStatus);
		}
		
		if ((VMProcess)(VMKernel.currentProcess()) == this)
		{
			processor.writeTLBEntry(tlbReplace, pageTable[vpn]);
		}
		return tlbReplace;
    }
    private boolean loadPage(int vpn,int ppn)
    {
    	int status = 0,v;

//		if ((additionPageTable[vpn].type == stack) ||
//			(additionPageTable[vpn].type == argument)||
//			(additionPageTable[vpn].type == cofffile &&
//			(!pageTable[vpn].readOnly)))
//		{
			if (additionPageTable[vpn].swapPage != -1)
			{
				int pos = additionPageTable[vpn].swapPage * pageSize;
				byte[] memory = Machine.processor().getMemory();
				int offset = ppn * pageSize;
//				VMKernel.semaphoreSwapFile.P();
				status = VMKernel.swapFile.read(pos,memory,offset,pageSize);
				if (status==-1)
				{
					Lock lock = VMKernel.lock;
			    	lock.release();
				    this.hasError = true;
				    handleExit(errorCode);
					return false;
				}
//				VMKernel.semaphoreSwapFile.V();
			}
//    	}
			
		else if (additionPageTable[vpn].type == cofffile)
		{
			for (int s=0; s<coff.getNumSections(); s++) {
			    CoffSection section = coff.getSection(s);
			    for (int i=0; i<section.getLength(); i++) {
			    	v = section.getFirstVPN()+i;
			    	if (v==vpn)
			    	{
			    		pageTable[vpn].readOnly = section.isReadOnly();
			    		section.loadPage(i,ppn);
			    		break;
			    	}
			    }
			}
		}
    	pageTable[vpn].ppn = ppn;
    	pageTable[vpn].valid = true;
    	pageTable[vpn].used = true;
    	pageTable[vpn].dirty = false;
//    	Lock lockIpt = VMKernel.lockIpt;
//    	lockIpt.acquire();
    	VMKernel.invertPageTable[ppn].vpn= vpn;
//    	VMKernel.invertPageTable[ppn].used = true;
    	VMKernel.invertPageTable[ppn].process = this;
//    	lockIpt.release();
		return true;
    }
    protected int checkTlb(int vaddr)
    {
    	Processor processor = Machine.processor();
//    	Random random = new Random();
		int tlbSize = processor.getTLBSize();
		int vpn = vaddr/pageSize;
		TranslationEntry entry;		
		boolean hit = false;
		int tlbIndex = 0;
		for(int i = 0;i < tlbSize;i++)
		{
			entry = processor.readTLBEntry(i);
			if (entry.valid && (entry.vpn == vpn)
				&&(entry.ppn == pageTable[vpn].ppn))
			{
				hit = true;
				tlbIndex = i;
				Lock lock = VMKernel.lock;
		    	lock.acquire();
		    	VMKernel.invertPageTable[pageTable[vpn].ppn].pinned= true;
		    	VMKernel.numPinned++;
		    	lock.release();
				break;
			}
		}
		if (!hit)
		{
			Lock lock = VMKernel.lock;
		   	lock.acquire();
		   	tlbIndex = handleTlbMiss(vaddr);
		   	VMKernel.invertPageTable[pageTable[vpn].ppn].pinned= true;
		   	VMKernel.numPinned++;
		   	lock.release();
		}
//			if ((VMProcess)(VMKernel.currentProcess()) == this)
//			{
//				processor.writeTLBEntry(tlbIndex, pageTable[vpn]);
//			}		    	
		return tlbIndex;
    }
    protected void changeAccessStatus(int vpn,int tlbIndex,boolean write)
    {
    	Processor processor = Machine.processor();
    	/*
    	Lock lock = VMKernel.lock;
        lock.acquire();
        VMKernel.invertPageTable[pageTable[vpn].ppn].used= true;
        lock.release();*/
    	TranslationEntry entry = pageTable[vpn];
    	entry.used = true;
    	if (!entry.dirty)
    	{
    		entry.dirty = write;
    	}
    	if ((VMProcess)(VMKernel.currentProcess()) == this)
		{
    		processor.writeTLBEntry(tlbIndex, entry);
		}
    }    
    protected void unPin(int vpn)
    {
      	Lock lock = VMKernel.lock;
    	lock.acquire();
    	VMKernel.invertPageTable[pageTable[vpn].ppn].pinned= false;
    	VMKernel.numPinned--;
    	VMKernel.cond.wake();
    	lock.release();

    }
    
    public boolean getUsed(int vpn)
    {
    	return pageTable[vpn].used;
    }
    public void setUsed(int vpn,boolean used)
    {
    	pageTable[vpn].used = used;
    }
    /*
    private void printPhysicalMemory()
    {
    	
    	boolean intStatus = Machine.interrupt().disable();
    	for (int i = 0;i<VMKernel.invertPageTable.length;i++)
    	{
    	System.out.println(" ppn: " + VMKernel.invertPageTable[i].ppn
    			          + " vpn: " + VMKernel.invertPageTable[i].vpn
    			          + " pinned" + VMKernel.invertPageTable[i].pinned
    			          + " used: " + VMKernel.invertPageTable[i].used
    			);
    	if (VMKernel.invertPageTable[i].process != null)
    	{
    	System.out.println(" process:" +VMKernel.invertPageTable[i].process.pid
		                 + " dirty: " + VMKernel.invertPageTable[i].process.pageTable[VMKernel.invertPageTable[i].vpn].dirty
			         
    			);
    	}
    	}
    	//lockIpt.release();
    	Machine.interrupt().restore(intStatus); 
    }
  */  
    /*jill end*/
    private static final int pageSize = Processor.pageSize;
    private static final char dbgProcess = 'a';
    private static final char dbgVM = 'v';
    /*jill add*/
    private AdditionInfoTE[] additionPageTable = null;
 //   private static Lock lockTlb = null;
    public static final int stack = 0;
    public static final int argument = 1;
    public static final int cofffile = 2;
    /*jill end*/
}